import json
import os
import random
from datetime import datetime,timedelta
import time
import plotly.graph_objects as go
import pandas as pd
import cv2

from utils.errorManager import FatigueManager


class Data_Collector(object):

    def __init__(self):
        self.keyPress = ['A','S','D']
        self.pre_generateTime = "" #上一次提示音的产生时间
        self.is_generate = False  #是否已产生下一个提示音
        self.sec = ""  #下一个提示音到来的秒数
        self.click_id = ""  #哪个按键的提示音

        self.sec_temp = 0  #和sec对比，用来产生提示音
        '''当个beat_table，fatigue_manager对象，只适合于单机任务，不能支持多个用户在线同时采集数据'''
        self.beat_table = Beat_Response_Table()
        self.fatigue_manager = FatigueManager()  #误差计算管理类
        '''设置疲劳阈值'''
        self.fatigue_manager.Threshold_Dalay = 0.7
        self.fatigue_manager.Threshold_PressMiss = 0.3
        self.fatigue_manager.Threshold_PressError = 0.3

        self.SAVE_DIR = "dataset/"
        self.TEMP_DIR = self.SAVE_DIR + "video/temp/"
        self.WINDOW_SIZE = 5  #滑动窗口大小
        self.w_index = 0  #滑动窗口结束位置索引
        self.SAVE_VIDEO_FLAGs = []  #用来控制是否保存视频文件
        # self.VIDEO_ANNOTATION_TAGs = {}   #用来绑定视频流和标注文件路径, key为摄像头ip, value是路径
        self.fatigueState = "Alert State" #当前时间段的疲劳状态
        self.CAMERA_COUNTER = {}   #用来绑定视频流和对应的帧数，为了进行fatigue的标注，key为摄像头ip，value是帧数

        self.hand_annotation_path = ""  #一个PVT任务对应一个手动标注文件
        self.KSS_table = {"1":"极度警醒", "2":"非常警醒", "3":"警醒", "4":"比较警醒", "5":"不太警醒但也无困意",
                          "6":"有一些困意倾向", "7":"有困意，但是不需要努力保持清醒", "8":"有困意，且需要一定的努力保持清醒",
                          "9":"非常困倦，需要极大的努力保持清醒"}

    '''随机产生指定节拍'''
    def generateBeat(self):
        if(self.is_generate == False):
            self.sec = random.randint(1,2)  # 随机产生秒数(1~2) 时间主要取决于前端页面的请求时间间隔
            self.click_id = random.randint(1,len(self.keyPress))  # 随机产生指定编号，代表指定提示音
            print(f"sec = {self.sec}")
            self.is_generate = True

        #等待下一个提示音的到来
        if(self.sec_temp > self.sec):

            self.beat_table.beat_key.append(self.keyPress[self.click_id - 1])
            # cur_filename = str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
            cur_filename = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            self.beat_table.beat_startT.append(cur_filename)

            self.is_generate = False
            self.sec_temp = 0
            return self.click_id
        else:
            self.sec_temp += 1
            print(f"sec_temp = {self.sec_temp}")

        return None

    '''节拍响应处理'''
    def beatRespone(self,key):
        '''
        :param key: key为点击的按键
        :return:
        '''
        '''如果之前的节拍没有做出响应，则补上null，保证len(eat_resKey) + 1 =  len(beat_key)'''
        while(len(self.beat_table.beat_resKey) < len(self.beat_table.beat_key) - 1):

            # 获取下一次节拍产生的时间
            dt_str = self.beat_table.beat_startT[len(self.beat_table.beat_resKey) + 1]
            # 字符串转datetime
            dt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S')
            dt_str = (dt - timedelta(seconds=1)).strftime("%Y-%m-%d %H:%M:%S")  # 当前时间减去1s
            self.beat_table.beat_resKey.append(None)
            self.beat_table.beat_responeT.append(dt_str)

            if (len(self.beat_table.beat_resKey) >= self.WINDOW_SIZE):  # 当前响应的次数大于一个时间窗口长度时
                self.w_index += 1

        '''接受产生节拍后的第一个响应，目的是为了同步节拍'''
        if(len(self.beat_table.beat_resKey) + 1 == len(self.beat_table.beat_key)):
            self.beat_table.beat_resKey.append(key)
            # cur_filename = str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
            cur_filename = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            self.beat_table.beat_responeT.append(cur_filename)

            if (len(self.beat_table.beat_resKey) >= self.WINDOW_SIZE):  # 当前响应的次数大于一个时间窗口长度时
                self.w_index += 1


    '''获取最新num条数据,并以json格式返回'''
    def get_BeatTable(self,num):
        temp = Beat_Response_Table()
        temp.beat_key = self.beat_table.beat_key[-num:]
        temp.beat_resKey = self.beat_table.beat_resKey[-num:]
        temp.beat_startT = self.beat_table.beat_startT[-num:]
        temp.beat_responeT = self.beat_table.beat_responeT[-num:]

        table_json = json.dumps(temp.__dict__)
        return table_json

    '''根据产生的节拍和响应的节拍，将数据保存为csv格式'''
    def save_beat_response(self):

        self.w_index = 0  #不显示疲劳状态
        df = pd.DataFrame(columns=('beat_startT', 'beat_key', 'beat_responeT', 'beat_resKey'))  # 生成空的panda
        # df_temp = [datetime, location, emotion]

        if(len(self.beat_table.beat_key) > 0):
            startT = str(self.beat_table.beat_startT[0]).replace(":","-").replace(" ","_")
            endT = str(self.beat_table.beat_startT[-1:][0]).replace(":","-").replace(" ","_")
            path = self.SAVE_DIR + startT + "__" + endT + ".csv"
            self.hand_annotation_path = self.SAVE_DIR + "hand_annotation/" + startT + "__" + endT + ".csv"

        for index in range(len(self.beat_table.beat_resKey)):
            df_temp = [self.beat_table.beat_startT[index], self.beat_table.beat_key[index],self.beat_table.beat_responeT[index],self.beat_table.beat_resKey[index]]
            df.loc[len(df)] = df_temp

        if(path != None):
            df.to_csv(path,index=False)

        return df


    '''数据预处理: 将字符修改为数字'''
    def preProcess(self, df, columns):
        '''
        :param df:
        :param columns: 要预处理的列
        :return:
        '''
        key_dict = {}
        key_dict['None'] = 0
        for index,char in enumerate(self.keyPress):
            key_dict[char] = index + 1

        #先将空值全部转化为None
        df = df.fillna("None")

        #参考 https://www.cnblogs.com/bigtreei/p/12463058.html
        for index in df.index:
            for column in columns:
                char = df.loc[index,column]
                df.loc[index,column] = key_dict[char]  #不要使用loc[index][column]，会抛出异常，影响df值的修改
                # print(df.loc[index,column])

        # print(df)
        return df


    '''将df转化成符合lineChart输入的数据格式'''
    def df_2_lineChart(self, df, x_columns, y_columns):
        '''
            :param df: 报表
            :param x_column: x坐标所对应的df列, type=字符串
            :param y_columns: y坐标所对应的df列，type=字符串list
            :return: x_axis  x坐标，type=list
            :return: y_axis  y坐标，type=list
            '''
        x_axis = []
        for x_column in x_columns:
            x_axis.append(df[x_column].values.tolist())
        y_axis = []
        for y_column in y_columns:
            y_axis.append((df[y_column].values).tolist())

        return x_axis, y_axis


    '''根据产生的节拍和响应的节拍，绘制折线图(x=时间，y=按键 + None)'''
    def beat_response_lineChart_plot(self, xs, ys, label_names, fontSize=20):
        fig = go.Figure()

        for index, y in enumerate(ys):
            fig.add_trace(go.Scatter(
                name=label_names[index],
                x=xs[index],
                y=y,
            ))

        fig.update_layout(
            font_size=fontSize,
            yaxis=dict(
                tickmode='array',
                tickvals=[0, 1, 2, 3],
                ticktext=['None', 'A', 'S', 'D']
            )
        )
        fig.show()

    '''误差分析，疲劳估计'''
    def calculate_error(self):

        bt = slice_beat_table_2_df(self.beat_table,self.w_index + self.WINDOW_SIZE,self.WINDOW_SIZE)
        delay = self.fatigue_manager.calculate_pressDelay(bt)
        fault = self.fatigue_manager.calculate_errorPress(bt)
        miss = self.fatigue_manager.calculate_missPress(bt)

        # self.w_index += 1  #有响应时，才会递增

        print(f"delay={self.fatigue_manager.delay}, error={self.fatigue_manager.pressError}, miss={self.fatigue_manager.pressMiss}")

    '''根据两条曲线的差异程度(相关性, 延迟情况, 误判别个数)，通过阈值判断是否疲劳'''
    def fatigue_judgement(self):

        if(len(self.beat_table.beat_resKey) >= self.WINDOW_SIZE):  #当前响应的次数大于一个时间窗口长度时
            self.calculate_error()

        temp = ""
        '''疲劳判断（级别从高到低分）'''
        if (self.fatigue_manager.pressMiss >= self.fatigue_manager.Threshold_PressMiss):
            temp = "Fatigue Level3"
        elif (self.fatigue_manager.pressError >= self.fatigue_manager.Threshold_PressError):
            temp = "Fatigue Level2"
        elif(self.fatigue_manager.delay >= self.fatigue_manager.Threshold_Dalay):
            temp = "Fatigue Level1"
        else:
            temp = "Alert state"

        self.fatigueState = temp
        return temp

    def save_monitorVideo(self, camera, ip):
        if(ip == '0'):
            self.save_monitorVideo_local(camera,ip)
        else:
            self.save_monitorVideo_webCam(camera,ip)

    '''本地视频数据保存'''
    def save_monitorVideo_local(self,camera,ip):
        '''
        :param camera: 摄像头对象
        :param ip: 摄像头ip地址
        :return:
        '''
        camera.set(cv2.CAP_PROP_FRAME_WIDTH,640)
        camera.set(cv2.CAP_PROP_FRAME_HEIGHT,480)
        fourcc = cv2.VideoWriter_fourcc('I','4','2','0')

        '''为了尽量让video的起始时间准确，方便和响应数据的同步，进行自动标注'''
        startT = str(self.beat_table.beat_startT[0]).replace(":", "-").replace(" ", "_")
        video_path = self.SAVE_DIR + "video/" + ip + "/" + startT + ".mp4"
        self.CAMERA_COUNTER[ip] = 0
        font = cv2.FONT_HERSHEY_SIMPLEX

        df = pd.DataFrame(columns=('frame_count', 'state'))
        annotation_path = self.SAVE_DIR + "auto_annotation/" + ip + "/" + startT + ".csv"

        # 多个摄像头公用同一个手动标注的文件
        # hand_annotation_path = self.SAVE_DIR + "hand_annotation/" + ip + "/" + startT + ".csv"
        # self.hand_annotation_path.append(hand_annotation_path)
        out = cv2.VideoWriter(video_path,fourcc,30,(640,480))
        while(camera.isOpened() and all(self.SAVE_VIDEO_FLAGs) == True):
            ret,frame = camera.read()
            if ret == True:
                self.CAMERA_COUNTER[ip] = self.CAMERA_COUNTER[ip] + 1

                '''疲劳级别'''
                df_temp = [str(self.CAMERA_COUNTER[ip]),self.fatigueState]
                df.loc[len(df)] = df_temp

                cv2.putText(frame, str(self.CAMERA_COUNTER[ip]), (60, 30), font, 1, (0, 0, 255), 2)
                cv2.putText(frame, self.fatigueState, (60, 60), font, 1, (0, 0, 255), 2)

                cv2.imshow("res" + ip,frame)
                out.write(frame)

                c = cv2.waitKey(10)
                if(c == 27): #esc键退出
                    break

        cv2.destroyAllWindows()
        df.to_csv(annotation_path,index=False)
        out.release()
        camera.release()

    '''网络视频数据保存'''
    def save_monitorVideo_webCam(self, camera, ip):
        ''''''
        '''逐帧生成图片'''
        self.CAMERA_COUNTER[ip] = 0

        startT = str(self.beat_table.beat_startT[0]).replace(":", "-").replace(" ", "_")
        font = cv2.FONT_HERSHEY_SIMPLEX
        df = pd.DataFrame(columns=('frame_count', 'state'))
        annotation_path = self.SAVE_DIR + "auto_annotation/" + ip + "/" + startT + ".csv"

        #手动标注的文件路径
        # hand_annotation_path = self.SAVE_DIR + "hand_annotation/" + ip + "/" + startT + ".csv"
        # self.hand_annotation_path.append(hand_annotation_path)
        while (camera.isOpened() and all(self.SAVE_VIDEO_FLAGs) == True):
            ret, frame = camera.read()
            if ret == True:
                self.CAMERA_COUNTER[ip] = self.CAMERA_COUNTER[ip] + 1
                frame = cv2.resize(frame, (640, 480))

                '''疲劳级别'''
                df_temp = [str(self.CAMERA_COUNTER[ip]), self.fatigueState]
                df.loc[len(df)] = df_temp

                cv2.putText(frame, str(self.CAMERA_COUNTER[ip]), (60, 30), font, 1, (0, 0, 255), 2)
                cv2.putText(frame, self.fatigueState, (60, 60), font, 1, (0, 0, 255), 2)

                cv2.imshow("res" + ip, frame)
                cv2.imwrite(self.TEMP_DIR + str(self.CAMERA_COUNTER[ip]).zfill(6) + ".jpg", frame)

                c = cv2.waitKey(10)
                if (c == 27):  # esc键退出
                    break

        cv2.destroyAllWindows()
        df.to_csv(annotation_path, index=False)
        camera.release()

        '''根据连续的图片处理成视频'''
        num = self.CAMERA_COUNTER[ip]
        fourcc = cv2.VideoWriter_fourcc('I', '4', '2', '0')  # opencv3.0
        video_path = self.SAVE_DIR + "video/" + ip + "/" + startT + ".mp4"
        out = cv2.VideoWriter(video_path, fourcc, 30, (640, 480))

        '''图片路径下的图片命名格式为000001.jpg 000002.jpg ……. 00100.jpg……'''
        for i in range(1, num):
            im_name = os.path.join(self.TEMP_DIR, str(i).zfill(6) + '.jpg')  # Python zfill() 方法返回指定长度的字符串，原字符串右对齐，前面填充0
            frame = cv2.imread(im_name)
            out.write(frame)
            print(im_name)

        out.release()
        print('finish')

    '''根据KSS量表编号，手动标注疲劳状态'''
    def hand_annotate_fatigue(self,KSS_id):
        df = pd.DataFrame(columns=('KSS', 'note'))
        df_temp = [KSS_id,self.KSS_table[KSS_id]]
        df.loc[len(df)] = df_temp
        # for path in self.hand_annotation_path:
        df.to_csv(self.hand_annotation_path,index=False)

        self.hand_annotation_path = ""

'''节拍响应表'''
class Beat_Response_Table(object):
    def __init__(self):
        self.beat_startT = []  #节拍开始时间
        self.beat_key = []  # 节拍要求键入
        self.beat_responeT = []  #节拍响应时间
        self.beat_resKey = []  #节拍实际键入


'''根据索引，滑动窗口大小对beat_table进行切片，并以dataframe返回'''
def slice_beat_table_2_df(beat_table,w_index,windowSize):
    '''
    :beat_table: Beat_Response_Table对象
    :w_index: 滑动窗口结束位置索引
    :param windowSize: 滑动窗口大小
    :return: dataframe
    '''
    bt = Beat_Response_Table()
    bt.beat_startT = beat_table.beat_startT[w_index - windowSize : w_index]
    bt.beat_key = beat_table.beat_key[w_index - windowSize : w_index]
    bt.beat_responeT = beat_table.beat_responeT[w_index - windowSize : w_index]
    bt.beat_resKey = beat_table.beat_resKey[w_index - windowSize : w_index]

    df = pd.DataFrame(columns=('beat_startT', 'beat_key', 'beat_responeT', 'beat_resKey'))  # 生成空的panda
    for index in range(len(bt.beat_responeT)):
        df_temp = [bt.beat_startT[index], bt.beat_key[index], bt.beat_responeT[index], bt.beat_resKey[index]]
        df.loc[len(df)] = df_temp

    return df

'''将df转化成beat_table对象'''
def df_2_beat_table(df):
    '''
    :param: dataframe
    :return: Beat_Response_Table
    '''
    bt = Beat_Response_Table()
    for index in df.index:
        bt.beat_startT.append(df.loc[index,'beat_startT'])
        bt.beat_key.append(df.loc[index, 'beat_key'])
        bt.beat_responeT.append(df.loc[index, 'beat_responeT'])
        bt.beat_resKey.append(df.loc[index, 'beat_resKey'])

    return bt

'''验证某个地址是否ping通'''
#<https://www.cnpython.com/qa/27807>
def check_ping(hostname):
    response = os.system("ping " + hostname)
    flag = False
    # and then check the response...
    if response == 0:
        pingstatus = "Network Active"
        flag = True
    else:
        pingstatus = "Network Error"
        flag = False

    return pingstatus,flag
